Completed
Branch master (6708a8)
by Rafael S.
07:49
created

WaveFileReader.readFactChunk_   A

Complexity

Conditions 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 10
rs 10
c 0
b 0
f 0
cc 2
1
/*
2
 * Copyright (c) 2017-2019 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a copy of this software and associated documentation files (the
6
 * "Software"), to deal in the Software without restriction, including
7
 * without limitation the rights to use, copy, modify, merge, publish,
8
 * distribute, sublicense, and/or sell copies of the Software, and to
9
 * permit persons to whom the Software is furnished to do so, subject to
10
 * the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
/**
26
 * @fileoverview The WaveFileReader class.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
/** @module wavefile */
31
32
import RIFFFile from './riff-file';
33
import {unpackString, unpack} from 'byte-data';
34
35
/**
36
 * A class to read wav files.
37
 */
38
export default class WaveFileReader extends RIFFFile {
39
40
  constructor() {
41
    super();
42
    // Include 'RF64' as a supported container format
43
    this.supported_containers.push('RF64');
44
    /**
45
     * The data of the 'fmt' chunk.
46
     * @type {!Object<string, *>}
47
     */
48
    this.fmt = {
49
      /** @type {string} */
50
      chunkId: '',
51
      /** @type {number} */
52
      chunkSize: 0,
53
      /** @type {number} */
54
      audioFormat: 0,
55
      /** @type {number} */
56
      numChannels: 0,
57
      /** @type {number} */
58
      sampleRate: 0,
59
      /** @type {number} */
60
      byteRate: 0,
61
      /** @type {number} */
62
      blockAlign: 0,
63
      /** @type {number} */
64
      bitsPerSample: 0,
65
      /** @type {number} */
66
      cbSize: 0,
67
      /** @type {number} */
68
      validBitsPerSample: 0,
69
      /** @type {number} */
70
      dwChannelMask: 0,
71
      /**
72
       * 4 32-bit values representing a 128-bit ID
73
       * @type {!Array<number>}
74
       */
75
      subformat: []
76
    };
77
    /**
78
     * The data of the 'fact' chunk.
79
     * @type {!Object<string, *>}
80
     */
81
    this.fact = {
82
      /** @type {string} */
83
      chunkId: '',
84
      /** @type {number} */
85
      chunkSize: 0,
86
      /** @type {number} */
87
      dwSampleLength: 0
88
    };
89
    /**
90
     * The data of the 'cue ' chunk.
91
     * @type {!Object<string, *>}
92
     */
93
    this.cue = {
94
      /** @type {string} */
95
      chunkId: '',
96
      /** @type {number} */
97
      chunkSize: 0,
98
      /** @type {number} */
99
      dwCuePoints: 0,
100
      /** @type {!Array<!Object>} */
101
      points: [],
102
    };
103
    /**
104
     * The data of the 'smpl' chunk.
105
     * @type {!Object<string, *>}
106
     */
107
    this.smpl = {
108
      /** @type {string} */
109
      chunkId: '',
110
      /** @type {number} */
111
      chunkSize: 0,
112
      /** @type {number} */
113
      dwManufacturer: 0,
114
      /** @type {number} */
115
      dwProduct: 0,
116
      /** @type {number} */
117
      dwSamplePeriod: 0,
118
      /** @type {number} */
119
      dwMIDIUnityNote: 0,
120
      /** @type {number} */
121
      dwMIDIPitchFraction: 0,
122
      /** @type {number} */
123
      dwSMPTEFormat: 0,
124
      /** @type {number} */
125
      dwSMPTEOffset: 0,
126
      /** @type {number} */
127
      dwNumSampleLoops: 0,
128
      /** @type {number} */
129
      dwSamplerData: 0,
130
      /** @type {!Array<!Object>} */
131
      loops: []
132
    };
133
    /**
134
     * The data of the 'bext' chunk.
135
     * @type {!Object<string, *>}
136
     */
137
    this.bext = {
138
      /** @type {string} */
139
      chunkId: '',
140
      /** @type {number} */
141
      chunkSize: 0,
142
      /** @type {string} */
143
      description: '', //256
144
      /** @type {string} */
145
      originator: '', //32
146
      /** @type {string} */
147
      originatorReference: '', //32
148
      /** @type {string} */
149
      originationDate: '', //10
150
      /** @type {string} */
151
      originationTime: '', //8
152
      /**
153
       * 2 32-bit values, timeReference high and low
154
       * @type {!Array<number>}
155
       */
156
      timeReference: [0, 0],
157
      /** @type {number} */
158
      version: 0, //WORD
159
      /** @type {string} */
160
      UMID: '', // 64 chars
161
      /** @type {number} */
162
      loudnessValue: 0, //WORD
163
      /** @type {number} */
164
      loudnessRange: 0, //WORD
165
      /** @type {number} */
166
      maxTruePeakLevel: 0, //WORD
167
      /** @type {number} */
168
      maxMomentaryLoudness: 0, //WORD
169
      /** @type {number} */
170
      maxShortTermLoudness: 0, //WORD
171
      /** @type {string} */
172
      reserved: '', //180
173
      /** @type {string} */
174
      codingHistory: '' // string, unlimited
175
    };
176
    /**
177
     * The data of the 'ds64' chunk.
178
     * Used only with RF64 files.
179
     * @type {!Object<string, *>}
180
     */
181
    this.ds64 = {
182
      /** @type {string} */
183
      chunkId: '',
184
      /** @type {number} */
185
      chunkSize: 0,
186
      /** @type {number} */
187
      riffSizeHigh: 0, // DWORD
188
      /** @type {number} */
189
      riffSizeLow: 0, // DWORD
190
      /** @type {number} */
191
      dataSizeHigh: 0, // DWORD
192
      /** @type {number} */
193
      dataSizeLow: 0, // DWORD
194
      /** @type {number} */
195
      originationTime: 0, // DWORD
196
      /** @type {number} */
197
      sampleCountHigh: 0, // DWORD
198
      /** @type {number} */
199
      sampleCountLow: 0 // DWORD
200
      /** @type {number} */
201
      //'tableLength': 0, // DWORD
202
      /** @type {!Array<number>} */
203
      //'table': []
204
    };
205
    /**
206
     * The data of the 'data' chunk.
207
     * @type {!Object<string, *>}
208
     */
209
    this.data = {
210
      /** @type {string} */
211
      chunkId: '',
212
      /** @type {number} */
213
      chunkSize: 0,
214
      /** @type {!Uint8Array} */
215
      samples: new Uint8Array(0)
216
    };
217
    /**
218
     * The data of the 'LIST' chunks.
219
     * Each item in this list look like this:
220
     *  {
221
     *      chunkId: '',
222
     *      chunkSize: 0,
223
     *      format: '',
224
     *      subChunks: []
225
     *   }
226
     * @type {!Array<!Object>}
227
     */
228
    this.LIST = [];
229
    /**
230
     * The data of the 'junk' chunk.
231
     * @type {!Object<string, *>}
232
     */
233
    this.junk = {
234
      /** @type {string} */
235
      chunkId: '',
236
      /** @type {number} */
237
      chunkSize: 0,
238
      /** @type {!Array<number>} */
239
      chunkData: []
240
    };
241
    /**
242
     * @type {!Object}
243
     * @protected
244
     */
245
    this.uInt16 = {bits: 16, be: false};
246
  }
247
248
  /**
249
   * Set up the WaveFileReader object from a byte buffer.
250
   * @param {!Uint8Array} wavBuffer The buffer.
251
   * @param {boolean=} samples True if the samples should be loaded.
252
   * @throws {Error} If container is not RIFF, RIFX or RF64.
253
   * @throws {Error} If format is not WAVE.
254
   * @throws {Error} If no 'fmt ' chunk is found.
255
   * @throws {Error} If no 'data' chunk is found.
256
   * @ignore
257
   */
258
  fromBuffer(wavBuffer, samples=true) {
259
    // Always should reset the chunks when reading from a buffer
260
    this.clearHeaders();
261
    this.setSignature(wavBuffer);
262
    this.uInt16.be = this.uInt32.be;
263
    if (this.format != 'WAVE') {
264
      throw Error('Could not find the "WAVE" format identifier');
265
    }
266
    this.readDs64Chunk_(wavBuffer);
267
    this.readFmtChunk_(wavBuffer);
268
    this.readFactChunk_(wavBuffer);
269
    this.readBextChunk_(wavBuffer);
270
    this.readCueChunk_(wavBuffer);
271
    this.readSmplChunk_(wavBuffer);
272
    this.readDataChunk_(wavBuffer, samples);
273
    this.readJunkChunk_(wavBuffer);
274
    this.readLISTChunk_(wavBuffer);
275
  }
276
277
  /**
278
   * Reset the chunks of the WaveFileReader instance.
279
   * @protected
280
   * @ignore
281
   */
282
  clearHeaders() {
283
    let tmpWav = new WaveFileReader();
284
    Object.assign(this.fmt, tmpWav.fmt);
285
    Object.assign(this.fact, tmpWav.fact);
286
    Object.assign(this.cue, tmpWav.cue);
287
    Object.assign(this.smpl, tmpWav.smpl);
288
    Object.assign(this.bext, tmpWav.bext);
289
    Object.assign(this.ds64, tmpWav.ds64);
290
    Object.assign(this.data, tmpWav.data);
291
    this.LIST = [];
292
    Object.assign(this.junk, tmpWav.junk);
293
  }
294
  
295
  /**
296
   * Read the 'fmt ' chunk of a wave file.
297
   * @param {!Uint8Array} buffer The wav file buffer.
298
   * @throws {Error} If no 'fmt ' chunk is found.
299
   * @private
300
   */
301
  readFmtChunk_(buffer) {
302
    /** @type {?Object} */
303
    let chunk = this.findChunk('fmt ');
304
    if (chunk) {
305
      this.head = chunk.chunkData.start;
306
      this.fmt.chunkId = chunk.chunkId;
307
      this.fmt.chunkSize = chunk.chunkSize;
308
      this.fmt.audioFormat = this.readUInt16_(buffer);
309
      this.fmt.numChannels = this.readUInt16_(buffer);
310
      this.fmt.sampleRate = this.readUInt32(buffer);
311
      this.fmt.byteRate = this.readUInt32(buffer);
312
      this.fmt.blockAlign = this.readUInt16_(buffer);
313
      this.fmt.bitsPerSample = this.readUInt16_(buffer);
314
      this.readFmtExtension_(buffer);
315
    } else {
316
      throw Error('Could not find the "fmt " chunk');
317
    }
318
  }
319
320
  /**
321
   * Read the 'fmt ' chunk extension.
322
   * @param {!Uint8Array} buffer The wav file buffer.
323
   * @private
324
   */
325
  readFmtExtension_(buffer) {
326
    if (this.fmt.chunkSize > 16) {
327
      this.fmt.cbSize = this.readUInt16_(buffer);
328
      if (this.fmt.chunkSize > 18) {
329
        this.fmt.validBitsPerSample = this.readUInt16_(buffer);
330
        if (this.fmt.chunkSize > 20) {
331
          this.fmt.dwChannelMask = this.readUInt32(buffer);
332
          this.fmt.subformat = [
333
            this.readUInt32(buffer),
334
            this.readUInt32(buffer),
335
            this.readUInt32(buffer),
336
            this.readUInt32(buffer)];
337
        }
338
      }
339
    }
340
  }
341
342
  /**
343
   * Read the 'fact' chunk of a wav file.
344
   * @param {!Uint8Array} buffer The wav file buffer.
345
   * @private
346
   */
347
  readFactChunk_(buffer) {
348
    /** @type {?Object} */
349
    let chunk = this.findChunk('fact');
350
    if (chunk) {
351
      this.head = chunk.chunkData.start;
352
      this.fact.chunkId = chunk.chunkId;
353
      this.fact.chunkSize = chunk.chunkSize;
354
      this.fact.dwSampleLength = this.readUInt32(buffer);
355
    }
356
  }
357
358
  /**
359
   * Read the 'cue ' chunk of a wave file.
360
   * @param {!Uint8Array} buffer The wav file buffer.
361
   * @private
362
   */
363
  readCueChunk_(buffer) {
364
    /** @type {?Object} */
365
    let chunk = this.findChunk('cue ');
366
    if (chunk) {
367
      this.head = chunk.chunkData.start;
368
      this.cue.chunkId = chunk.chunkId;
369
      this.cue.chunkSize = chunk.chunkSize;
370
      this.cue.dwCuePoints = this.readUInt32(buffer);
371
      for (let i = 0; i < this.cue.dwCuePoints; i++) {
372
        this.cue.points.push({
373
          dwName: this.readUInt32(buffer),
374
          dwPosition: this.readUInt32(buffer),
375
          fccChunk: this.readString(buffer, 4),
376
          dwChunkStart: this.readUInt32(buffer),
377
          dwBlockStart: this.readUInt32(buffer),
378
          dwSampleOffset: this.readUInt32(buffer),
379
        });
380
      }
381
    }
382
  }
383
384
  /**
385
   * Read the 'smpl' chunk of a wave file.
386
   * @param {!Uint8Array} buffer The wav file buffer.
387
   * @private
388
   */
389
  readSmplChunk_(buffer) {
390
    /** @type {?Object} */
391
    let chunk = this.findChunk('smpl');
392
    if (chunk) {
393
      this.head = chunk.chunkData.start;
394
      this.smpl.chunkId = chunk.chunkId;
395
      this.smpl.chunkSize = chunk.chunkSize;
396
      this.smpl.dwManufacturer = this.readUInt32(buffer);
397
      this.smpl.dwProduct = this.readUInt32(buffer);
398
      this.smpl.dwSamplePeriod = this.readUInt32(buffer);
399
      this.smpl.dwMIDIUnityNote = this.readUInt32(buffer);
400
      this.smpl.dwMIDIPitchFraction = this.readUInt32(buffer);
401
      this.smpl.dwSMPTEFormat = this.readUInt32(buffer);
402
      this.smpl.dwSMPTEOffset = this.readUInt32(buffer);
403
      this.smpl.dwNumSampleLoops = this.readUInt32(buffer);
404
      this.smpl.dwSamplerData = this.readUInt32(buffer);
405
      for (let i = 0; i < this.smpl.dwNumSampleLoops; i++) {
406
        this.smpl.loops.push({
407
          dwName: this.readUInt32(buffer),
408
          dwType: this.readUInt32(buffer),
409
          dwStart: this.readUInt32(buffer),
410
          dwEnd: this.readUInt32(buffer),
411
          dwFraction: this.readUInt32(buffer),
412
          dwPlayCount: this.readUInt32(buffer),
413
        });
414
      }
415
    }
416
  }
417
418
  /**
419
   * Read the 'data' chunk of a wave file.
420
   * @param {!Uint8Array} buffer The wav file buffer.
421
   * @param {boolean} samples True if the samples should be loaded.
422
   * @throws {Error} If no 'data' chunk is found.
423
   * @private
424
   */
425
  readDataChunk_(buffer, samples) {
426
    /** @type {?Object} */
427
    let chunk = this.findChunk('data');
428
    if (chunk) {
429
      this.data.chunkId = 'data';
430
      this.data.chunkSize = chunk.chunkSize;
431
      if (samples) {
432
        this.data.samples = buffer.slice(
433
          chunk.chunkData.start,
434
          chunk.chunkData.end);
435
      }
436
    } else {
437
      throw Error('Could not find the "data" chunk');
438
    }
439
  }
440
441
  /**
442
   * Read the 'bext' chunk of a wav file.
443
   * @param {!Uint8Array} buffer The wav file buffer.
444
   * @private
445
   */
446
  readBextChunk_(buffer) {
447
    /** @type {?Object} */
448
    let chunk = this.findChunk('bext');
449
    if (chunk) {
450
      this.head = chunk.chunkData.start;
451
      this.bext.chunkId = chunk.chunkId;
452
      this.bext.chunkSize = chunk.chunkSize;
453
      this.bext.description = this.readString(buffer, 256);
454
      this.bext.originator = this.readString(buffer, 32);
455
      this.bext.originatorReference = this.readString(buffer, 32);
456
      this.bext.originationDate = this.readString(buffer, 10);
457
      this.bext.originationTime = this.readString(buffer, 8);
458
      this.bext.timeReference = [
459
        this.readUInt32(buffer),
460
        this.readUInt32(buffer)];
461
      this.bext.version = this.readUInt16_(buffer);
462
      this.bext.UMID = this.readString(buffer, 64);
463
      this.bext.loudnessValue = this.readUInt16_(buffer);
464
      this.bext.loudnessRange = this.readUInt16_(buffer);
465
      this.bext.maxTruePeakLevel = this.readUInt16_(buffer);
466
      this.bext.maxMomentaryLoudness = this.readUInt16_(buffer);
467
      this.bext.maxShortTermLoudness = this.readUInt16_(buffer);
468
      this.bext.reserved = this.readString(buffer, 180);
469
      this.bext.codingHistory = this.readString(
470
        buffer, this.bext.chunkSize - 602);
471
    }
472
  }
473
474
  /**
475
   * Read the 'ds64' chunk of a wave file.
476
   * @param {!Uint8Array} buffer The wav file buffer.
477
   * @throws {Error} If no 'ds64' chunk is found and the file is RF64.
478
   * @private
479
   */
480
  readDs64Chunk_(buffer) {
481
    /** @type {?Object} */
482
    let chunk = this.findChunk('ds64');
483
    if (chunk) {
484
      this.head = chunk.chunkData.start;
485
      this.ds64.chunkId = chunk.chunkId;
486
      this.ds64.chunkSize = chunk.chunkSize;
487
      this.ds64.riffSizeHigh = this.readUInt32(buffer);
488
      this.ds64.riffSizeLow = this.readUInt32(buffer);
489
      this.ds64.dataSizeHigh = this.readUInt32(buffer);
490
      this.ds64.dataSizeLow = this.readUInt32(buffer);
491
      this.ds64.originationTime = this.readUInt32(buffer);
492
      this.ds64.sampleCountHigh = this.readUInt32(buffer);
493
      this.ds64.sampleCountLow = this.readUInt32(buffer);
494
      //if (wav.ds64.chunkSize > 28) {
495
      //  wav.ds64.tableLength = unpack(
496
      //    chunkData.slice(28, 32), uInt32_);
497
      //  wav.ds64.table = chunkData.slice(
498
      //     32, 32 + wav.ds64.tableLength);
499
      //}
500
    } else {
501
      if (this.container == 'RF64') {
502
        throw Error('Could not find the "ds64" chunk');
503
      }
504
    }
505
  }
506
507
  /**
508
   * Read the 'LIST' chunks of a wave file.
509
   * @param {!Uint8Array} buffer The wav file buffer.
510
   * @private
511
   */
512
  readLISTChunk_(buffer) {
513
    /** @type {?Object} */
514
    let listChunks = this.findChunk('LIST', true);
515
    if (listChunks !== null) {
516
      for (let j=0; j < listChunks.length; j++) {
517
        /** @type {!Object} */
518
        let subChunk = listChunks[j];
519
        this.LIST.push({
520
          chunkId: subChunk.chunkId,
521
          chunkSize: subChunk.chunkSize,
522
          format: subChunk.format,
523
          subChunks: []});
524
        for (let x=0; x<subChunk.subChunks.length; x++) {
525
          this.readLISTSubChunks_(subChunk.subChunks[x],
526
            subChunk.format, buffer);
527
        }
528
      }
529
    }
530
  }
531
532
  /**
533
   * Read the sub chunks of a 'LIST' chunk.
534
   * @param {!Object} subChunk The 'LIST' subchunks.
535
   * @param {string} format The 'LIST' format, 'adtl' or 'INFO'.
536
   * @param {!Uint8Array} buffer The wav file buffer.
537
   * @private
538
   */
539
  readLISTSubChunks_(subChunk, format, buffer) {
540
    if (format == 'adtl') {
541
      if (['labl', 'note','ltxt'].indexOf(subChunk.chunkId) > -1) {
542
        this.head = subChunk.chunkData.start;
543
        /** @type {!Object<string, string|number>} */
544
        let item = {
545
          chunkId: subChunk.chunkId,
546
          chunkSize: subChunk.chunkSize,
547
          dwName: this.readUInt32(buffer)
548
        };
549
        if (subChunk.chunkId == 'ltxt') {
550
          item.dwSampleLength = this.readUInt32(buffer);
551
          item.dwPurposeID = this.readUInt32(buffer);
552
          item.dwCountry = this.readUInt16_(buffer);
553
          item.dwLanguage = this.readUInt16_(buffer);
554
          item.dwDialect = this.readUInt16_(buffer);
555
          item.dwCodePage = this.readUInt16_(buffer);
556
        }
557
        item.value = this.readZSTR_(buffer, this.head);
558
        this.LIST[this.LIST.length - 1].subChunks.push(item);
559
      }
560
    // RIFF INFO tags like ICRD, ISFT, ICMT
561
    } else if(format == 'INFO') {
562
      this.head = subChunk.chunkData.start;
563
      this.LIST[this.LIST.length - 1].subChunks.push({
564
        chunkId: subChunk.chunkId,
565
        chunkSize: subChunk.chunkSize,
566
        value: this.readZSTR_(buffer, this.head)
567
      });
568
    }
569
  }
570
571
  /**
572
   * Read the 'junk' chunk of a wave file.
573
   * @param {!Uint8Array} buffer The wav file buffer.
574
   * @private
575
   */
576
  readJunkChunk_(buffer) {
577
    /** @type {?Object} */
578
    let chunk = this.findChunk('junk');
579
    if (chunk) {
580
      this.junk = {
581
        chunkId: chunk.chunkId,
582
        chunkSize: chunk.chunkSize,
583
        chunkData: [].slice.call(buffer.slice(
584
          chunk.chunkData.start,
585
          chunk.chunkData.end))
586
      };
587
    }
588
  }
589
590
  /**
591
   * Read bytes as a ZSTR string.
592
   * @param {!Uint8Array} bytes The bytes.
593
   * @param {number} index the index to start reading.
594
   * @return {string} The string.
595
   * @private
596
   */
597
  readZSTR_(bytes, index=0) {
598
    for (let i = index; i < bytes.length; i++) {
599
      this.head++;
600
      if (bytes[i] === 0) {
601
        break;
602
      }
603
    }
604
    return unpackString(bytes, index, this.head - 1);
605
  }
606
607
  /**
608
   * Read a number from a chunk.
609
   * @param {!Uint8Array} bytes The chunk bytes.
610
   * @return {number} The number.
611
   * @private
612
   */
613
  readUInt16_(bytes) {
614
    /** @type {number} */
615
    let value = unpack(bytes, this.uInt16, this.head);
616
    this.head += 2;
617
    return value;
618
  }
619
}
620